/*
 * Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

#include "iotsdk/nx_driver_cmsis_eth.h"
#include "Driver_Common.h"
#include "Driver_ETH.h"
#include "Driver_ETH_MAC.h"
#include "Driver_ETH_PHY.h"
#include "fff.h"
#include "nx_api.h"
#include "nx_arp.h"
#include "nx_ip.h"
#include "nx_rarp.h"
#include "tx_api.h"

#include "gtest/gtest.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

DEFINE_FFF_GLOBALS

#define ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE 2U
#define ETHERNET_FRAME_SIZE                 14

#define ETHER_PACKET_TYPE_IP   0x0800
#define ETHER_PACKET_TYPE_IPV6 0x86dd
#define ETHER_PACKET_TYPE_ARP  0x0806
#define ETHER_PACKET_TYPE_RARP 0x8035

#define PACKET_COUNT     12
#define PACKET_SIZE      1536
#define PACKET_POOL_SIZE ((PACKET_SIZE + sizeof(NX_PACKET)) * PACKET_COUNT)

static UINT
nx_packet_allocate_custom_fake(NX_PACKET_POOL *pool_ptr, NX_PACKET **packet_ptr, ULONG packet_type, ULONG wait_option);
static int32_t get_ethernet_controller_mac_addr_custom_fake(ARM_ETH_MAC_ADDR *mac_address);

static uint8_t gs_mac_address[6];
static uint8_t gs_packet_pool_buffer[PACKET_POOL_SIZE];
static size_t gs_mtu;
static void *gs_ctx;
static uint16_t gs_incomming_packet_eth_type;

static int32_t ARM_ETH_MAC_Initialize_custom_fake(ARM_ETH_MAC_SignalEvent_t cb_event)
{
    cb_event(ARM_ETH_MAC_EVENT_RX_FRAME);

    return ARM_DRIVER_OK;
}

class TestNxDriverCmsisEth : public ::testing::Test {
public:
    TestNxDriverCmsisEth()
    {
        RESET_FAKE(ARM_ETH_MAC_Initialize);
        RESET_FAKE(ARM_ETH_PHY_Initialize);
        RESET_FAKE(ARM_ETH_MAC_SetAddressFilter);
        RESET_FAKE(nx_ip_interface_mtu_set);
        RESET_FAKE(ARM_ETH_MAC_GetMacAddress);
        RESET_FAKE(nx_ip_interface_physical_address_set);
        RESET_FAKE(nx_ip_interface_address_mapping_configure);
        RESET_FAKE(ARM_ETH_PHY_GetLinkState);

        memset(gs_mac_address, 0xFFU, sizeof(gs_mac_address));
        memset(gs_packet_pool_buffer, 0, sizeof(gs_packet_pool_buffer));
        gs_mtu = 1518U;
        gs_ctx = nullptr;
        gs_incomming_packet_eth_type = UINT16_MAX;
    }
};

class TestNxDriverCmsisEthInit : public TestNxDriverCmsisEth {
public:
    TestNxDriverCmsisEthInit() : TestNxDriverCmsisEth()
    {
        initialize_attach_and_enable();
        RESET_FAKE(ARM_ETH_MAC_GetMacAddress);
        RESET_FAKE(ARM_ETH_PHY_GetLinkState);
        RESET_FAKE(ARM_ETH_MAC_SetAddressFilter);
        RESET_FAKE(ARM_ETH_MAC_Initialize);
        RESET_FAKE(ARM_ETH_PHY_Initialize);
        RESET_FAKE(tx_thread_identify);
        RESET_FAKE(tx_thread_preemption_change);
        RESET_FAKE(ARM_ETH_MAC_SendFrame);
        RESET_FAKE(_nx_ip_driver_deferred_processing);
        RESET_FAKE(ARM_ETH_MAC_GetRxFrameSize);
        RESET_FAKE(nx_packet_allocate);
        RESET_FAKE(nx_packet_release);
    }

private:
    void initialize_attach_and_enable(void)
    {
        struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
        NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_FALSE};
        NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_INITIALIZE,
                                     .nx_ip_driver_status = NX_DRIVER_ERROR,
                                     .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                     .nx_ip_driver_interface = &nx_ip_driver_interface};

        ARM_ETH_MAC_Initialize_fake.custom_fake = ARM_ETH_MAC_Initialize_custom_fake;
        ARM_ETH_PHY_Initialize_fake.return_val = ARM_DRIVER_OK;
        ARM_ETH_MAC_GetMacAddress_fake.custom_fake = get_ethernet_controller_mac_addr_custom_fake;

        nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
        EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);

        nx_ip_driver.nx_ip_driver_command = NX_LINK_INTERFACE_ATTACH;
        nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);

        nx_ip_driver.nx_ip_driver_command = NX_LINK_ENABLE;
        ARM_ETH_PHY_GetLinkState_fake.return_val = ARM_ETH_LINK_UP;
        nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
        EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
        EXPECT_EQ(NX_TRUE, nx_ip_driver_interface.nx_interface_link_up);
    }
};

TEST_F(TestNxDriverCmsisEth, hw_power_up_failure_fails_ip_driver_init)
{
    ARM_ETH_MAC_Initialize_fake.return_val = ARM_DRIVER_ERROR;

    NX_IP_DRIVER nx_ip_driver = {
        .nx_ip_driver_command = NX_LINK_INITIALIZE,
        .nx_ip_driver_status = NX_SUCCESS,
    };

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);

    EXPECT_EQ(NX_DRIVER_ERROR, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEth, hw_power_up_success_succeeds_ip_driver_init)
{

    ARM_ETH_MAC_Initialize_fake.return_val = ARM_DRIVER_OK;
    ARM_ETH_PHY_Initialize_fake.return_val = ARM_DRIVER_OK;

    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    UCHAR nx_interface_index = 67U;
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_index = nx_interface_index};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_INITIALIZE,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);

    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEth, hw_power_up_success_succeeds_sets_ip_interface_mtu)
{
    const size_t mtu = 1518U;

    ARM_ETH_MAC_Initialize_fake.return_val = ARM_DRIVER_OK;
    ARM_ETH_PHY_Initialize_fake.return_val = ARM_DRIVER_OK;

    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    UCHAR nx_interface_index = 67U;
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_index = nx_interface_index};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_INITIALIZE,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);

    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);

    EXPECT_EQ(&nx_ip_driver_ptr, nx_ip_interface_mtu_set_fake.arg0_val);
    EXPECT_EQ(nx_interface_index, nx_ip_interface_mtu_set_fake.arg1_val);
    EXPECT_EQ((mtu - ETHERNET_FRAME_SIZE), nx_ip_interface_mtu_set_fake.arg2_val);
}

static int32_t get_ethernet_controller_mac_addr_custom_fake(ARM_ETH_MAC_ADDR *mac_address)
{
    memcpy(mac_address, gs_mac_address, sizeof(gs_mac_address));
    return 0;
}

TEST_F(TestNxDriverCmsisEth, hw_power_up_success_succeeds_sets_ip_interface_mac)
{
    gs_mac_address[0] = 0xDE;
    gs_mac_address[1] = 0xAD;
    gs_mac_address[2] = 0xBE;
    gs_mac_address[3] = 0xEF;
    gs_mac_address[4] = 0xBA;
    gs_mac_address[5] = 0xBE;

    ARM_ETH_MAC_Initialize_fake.return_val = ARM_DRIVER_OK;
    ARM_ETH_PHY_Initialize_fake.return_val = ARM_DRIVER_OK;
    ARM_ETH_MAC_GetMacAddress_fake.custom_fake = get_ethernet_controller_mac_addr_custom_fake;

    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    UCHAR nx_interface_index = 67U;
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_index = nx_interface_index};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_INITIALIZE,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);

    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);

    EXPECT_EQ(&nx_ip_driver_ptr, nx_ip_interface_physical_address_set_fake.arg0_val);
    EXPECT_EQ(nx_interface_index, nx_ip_interface_physical_address_set_fake.arg1_val);
    EXPECT_EQ((uint32_t)((gs_mac_address[0] << 8) | (gs_mac_address[1])),
              nx_ip_interface_physical_address_set_fake.arg2_val);
    EXPECT_EQ((uint32_t)((gs_mac_address[2] << 24) | (gs_mac_address[3] << 16) | (gs_mac_address[4] << 8)
                         | (gs_mac_address[5])),
              nx_ip_interface_physical_address_set_fake.arg3_val);
    EXPECT_EQ(NX_FALSE, nx_ip_interface_physical_address_set_fake.arg4_val);
}

TEST_F(TestNxDriverCmsisEth, hw_power_up_success_succeeds_sets_ip_interface_address_mapping)
{
    ARM_ETH_MAC_Initialize_fake.return_val = ARM_DRIVER_OK;
    ARM_ETH_PHY_Initialize_fake.return_val = ARM_DRIVER_OK;
    ARM_ETH_MAC_GetMacAddress_fake.custom_fake = get_ethernet_controller_mac_addr_custom_fake;

    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    UCHAR nx_interface_index = 67U;
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_index = nx_interface_index};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_INITIALIZE,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);

    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);

    EXPECT_EQ(&nx_ip_driver_ptr, nx_ip_interface_address_mapping_configure_fake.arg0_val);
    EXPECT_EQ(nx_interface_index, nx_ip_interface_address_mapping_configure_fake.arg1_val);
    EXPECT_EQ(NX_TRUE, nx_ip_interface_address_mapping_configure_fake.arg2_val);
}

TEST_F(TestNxDriverCmsisEth, enabling_uninitialized_ip_driver_fails)
{
    NX_IP_DRIVER nx_ip_driver = {
        .nx_ip_driver_command = NX_LINK_ENABLE,
        .nx_ip_driver_status = NX_SUCCESS,
    };

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);

    EXPECT_EQ(NX_DRIVER_ERROR, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEth, enabling_initialized_ip_driver_indicates_link_up)
{
    ARM_ETH_MAC_Initialize_fake.return_val = ARM_DRIVER_OK;
    ARM_ETH_PHY_Initialize_fake.return_val = ARM_DRIVER_OK;
    ARM_ETH_MAC_GetMacAddress_fake.custom_fake = get_ethernet_controller_mac_addr_custom_fake;

    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    UCHAR nx_interface_index = 67U;
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_FALSE, .nx_interface_index = nx_interface_index};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_INITIALIZE,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);

    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);

    nx_ip_driver.nx_ip_driver_command = NX_LINK_ENABLE;

    ARM_ETH_PHY_GetLinkState_fake.return_val = ARM_ETH_LINK_UP;

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);

    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);

    EXPECT_EQ(NX_TRUE, nx_ip_driver_interface.nx_interface_link_up);
}

TEST_F(TestNxDriverCmsisEth, enabling_already_enabled_ip_driver_fails)
{
    ARM_ETH_MAC_Initialize_fake.return_val = ARM_DRIVER_OK;
    ARM_ETH_PHY_Initialize_fake.return_val = ARM_DRIVER_OK;
    ARM_ETH_MAC_GetMacAddress_fake.custom_fake = get_ethernet_controller_mac_addr_custom_fake;
    ARM_ETH_PHY_GetLinkState_fake.return_val = ARM_ETH_LINK_UP;

    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    UCHAR nx_interface_index = 67U;
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_FALSE, .nx_interface_index = nx_interface_index};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_INITIALIZE,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);

    nx_ip_driver.nx_ip_driver_command = NX_LINK_ENABLE;

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
    EXPECT_EQ(NX_TRUE, nx_ip_driver_interface.nx_interface_link_up);

    nx_ip_driver.nx_ip_driver_command = NX_LINK_ENABLE;
    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_ALREADY_ENABLED, nx_ip_driver.nx_ip_driver_status);
    EXPECT_EQ(NX_TRUE, nx_ip_driver_interface.nx_interface_link_up);
}

TEST_F(TestNxDriverCmsisEthInit, reenabling_disabled_ip_driver_succeeds)
{
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_DISABLE,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    ARM_ETH_PHY_GetLinkState_fake.return_val = ARM_ETH_LINK_DOWN;
    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
    EXPECT_EQ(NX_FALSE, nx_ip_driver_interface.nx_interface_link_up);

    ARM_ETH_PHY_GetLinkState_fake.return_val = ARM_ETH_LINK_UP;
    nx_ip_driver.nx_ip_driver_command = NX_LINK_ENABLE;
    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
    EXPECT_EQ(NX_TRUE, nx_ip_driver_interface.nx_interface_link_up);
}

TEST_F(TestNxDriverCmsisEthInit, disabling_enabled_ip_driver_indicates_link_down)
{
    ARM_ETH_PHY_GetLinkState_fake.return_val = ARM_ETH_LINK_DOWN;

    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_DISABLE,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_ip_driver.nx_ip_driver_command = NX_LINK_DISABLE;
    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
    EXPECT_EQ(NX_FALSE, nx_ip_driver_interface.nx_interface_link_up);
}

TEST_F(TestNxDriverCmsisEth, disable_when_link_down_leaves_unmodified_link_status)
{
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_FALSE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_DISABLE,
                                 .nx_ip_driver_status = NX_SUCCESS,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_DRIVER_ERROR, nx_ip_driver.nx_ip_driver_status);
    EXPECT_EQ(NX_FALSE, nx_ip_driver_interface.nx_interface_link_up);
}

TEST_F(TestNxDriverCmsisEth, current_link_status_is_accurately_reported)
{
    struct NX_IP_STRUCT nx_ip_driver_ptr = {.nx_ip_interface = {{
                                                .nx_interface_link_up = 0xDE // This member is what should be returned.
                                            }}};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_FALSE};
    ULONG link_status = 0xBEEFBABE;
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_GET_STATUS,
                                 .nx_ip_driver_status = NX_SUCCESS,
                                 .nx_ip_driver_return_ptr = &link_status,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
    EXPECT_EQ(link_status, nx_ip_driver.nx_ip_driver_ptr->nx_ip_interface[0].nx_interface_link_up);
}

TEST_F(TestNxDriverCmsisEth, multicast_join_when_link_down_fails)
{
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {0};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_MULTICAST_JOIN,
                                 .nx_ip_driver_status = NX_SUCCESS,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_NOT_ENABLED, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEth, multicast_leave_when_link_down_fails)
{
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {0};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_MULTICAST_LEAVE,
                                 .nx_ip_driver_status = NX_SUCCESS,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_NOT_ENABLED, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEthInit, multicast_join_enabled_ip_driver_succeeds)
{
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_MULTICAST_JOIN,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    ARM_ETH_MAC_SetAddressFilter_fake.return_val = ARM_DRIVER_OK;
    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEthInit, multicast_leave_enabled_ip_driver_succeeds)
{
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_MULTICAST_LEAVE,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    ARM_ETH_MAC_SetAddressFilter_fake.return_val = ARM_DRIVER_OK;
    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEthInit, multicast_join_enabled_ip_driver_fails_with_hw_error)
{
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_MULTICAST_JOIN,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    ARM_ETH_MAC_SetAddressFilter_fake.return_val = ARM_DRIVER_ERROR;

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(nx_ip_driver.nx_ip_driver_status, NX_DRIVER_ERROR);
    EXPECT_EQ(NX_TRUE, nx_ip_driver_interface.nx_interface_link_up);
}

TEST_F(TestNxDriverCmsisEthInit, multicast_leave_enabled_ip_driver_fails_with_hw_error)
{
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_MULTICAST_JOIN,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);

    ARM_ETH_MAC_SetAddressFilter_fake.return_val = ARM_DRIVER_ERROR;
    nx_ip_driver.nx_ip_driver_command = NX_LINK_MULTICAST_LEAVE;

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_DRIVER_ERROR, nx_ip_driver.nx_ip_driver_status);
    EXPECT_EQ(NX_TRUE, nx_ip_driver_interface.nx_interface_link_up);
}

TEST_F(TestNxDriverCmsisEth, send_when_link_down_fails)
{
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_FALSE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_PACKET_SEND,
                                 .nx_ip_driver_status = NX_SUCCESS,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_NOT_ENABLED, nx_ip_driver.nx_ip_driver_status);
    EXPECT_EQ(NX_FALSE, nx_ip_driver_interface.nx_interface_link_up);
}

TEST_F(TestNxDriverCmsisEthInit, ethernet_header_correctly_formed)
{
    tx_thread_preemption_change_fake.return_val = TX_SUCCESS;
    tx_thread_identify_fake.return_val = nullptr;

    UCHAR *payload_location = gs_packet_pool_buffer + ETHERNET_FRAME_SIZE + ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE;
    NX_PACKET outgoing_packet = {.nx_packet_prepend_ptr = payload_location,
                                 .nx_packet_append_ptr = payload_location + gs_mtu - ETHERNET_FRAME_SIZE
                                                         - ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE + 1,
                                 .nx_packet_length = gs_mtu - ETHERNET_FRAME_SIZE};
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE,
                                           .nx_interface_physical_address_msw = 0xFFFF99AA,
                                           .nx_interface_physical_address_lsw = 0xBBCCDDEE};
    NX_IP_DRIVER nx_ip_driver = {
        .nx_ip_driver_command = NX_LINK_PACKET_SEND,
        .nx_ip_driver_status = NX_DRIVER_ERROR,
        .nx_ip_driver_physical_address_msw = 0x11223344,
        .nx_ip_driver_physical_address_lsw = 0x55667788,
        .nx_ip_driver_packet = &outgoing_packet,
        .nx_ip_driver_ptr = &nx_ip_driver_ptr,
        .nx_ip_driver_interface = &nx_ip_driver_interface,
    };

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    const uint8_t ethernet_header[ETHERNET_FRAME_SIZE] = {// Destination
                                                          0x44,
                                                          0x33,
                                                          0x22,
                                                          0x11,
                                                          0x88,
                                                          0x77,
                                                          // Source
                                                          0x66,
                                                          0x55,
                                                          0xCC,
                                                          0xBB,
                                                          0xAA,
                                                          0x99,
                                                          // Ether type
                                                          0x00,
                                                          0x08};

    const void *buf =
        static_cast<const uint8_t *>(ARM_ETH_MAC_SendFrame_fake.arg0_val) - ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE;
    EXPECT_EQ(0, memcmp(buf, ethernet_header, ETHERNET_FRAME_SIZE));
}

TEST_F(TestNxDriverCmsisEthInit, outgoing_packet_exceeding_mtu_fails)
{
    UCHAR *payload_location = gs_packet_pool_buffer + ETHERNET_FRAME_SIZE + ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE;
    NX_PACKET outgoing_packet = {
        .nx_packet_prepend_ptr = payload_location,
        .nx_packet_append_ptr =
            payload_location + gs_mtu + 1 - ETHERNET_FRAME_SIZE - ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE + 1,
        .nx_packet_length = gs_mtu + 1 - ETHERNET_FRAME_SIZE,
    };
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_PACKET_SEND,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_packet = &outgoing_packet,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_DRIVER_ERROR, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEthInit, outgoing_ip_packet_up_to_mtu_succeeds)
{
    tx_thread_preemption_change_fake.return_val = TX_SUCCESS;
    tx_thread_identify_fake.return_val = nullptr;

    UCHAR *payload_location = gs_packet_pool_buffer + ETHERNET_FRAME_SIZE + ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE;
    NX_PACKET outgoing_packet = {.nx_packet_prepend_ptr = payload_location,
                                 .nx_packet_append_ptr = payload_location + gs_mtu - ETHERNET_FRAME_SIZE
                                                         - ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE + 1,
                                 .nx_packet_length = gs_mtu - ETHERNET_FRAME_SIZE};
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_PACKET_SEND,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_packet = &outgoing_packet,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEthInit, outgoing_arp_packet_up_to_mtu_succeeds)
{
    tx_thread_preemption_change_fake.return_val = TX_SUCCESS;
    tx_thread_identify_fake.return_val = nullptr;

    UCHAR *payload_location = gs_packet_pool_buffer + ETHERNET_FRAME_SIZE + ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE;
    NX_PACKET outgoing_packet = {.nx_packet_prepend_ptr = payload_location,
                                 .nx_packet_append_ptr = payload_location + gs_mtu - ETHERNET_FRAME_SIZE
                                                         - ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE + 1,
                                 .nx_packet_length = gs_mtu - ETHERNET_FRAME_SIZE};
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_ARP_SEND,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_packet = &outgoing_packet,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEthInit, outgoing_rarp_packet_up_to_mtu_succeeds)
{
    tx_thread_preemption_change_fake.return_val = TX_SUCCESS;
    tx_thread_identify_fake.return_val = nullptr;

    UCHAR *payload_location = gs_packet_pool_buffer + ETHERNET_FRAME_SIZE + ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE;
    NX_PACKET outgoing_packet = {.nx_packet_prepend_ptr = payload_location,
                                 .nx_packet_append_ptr = payload_location + gs_mtu - ETHERNET_FRAME_SIZE
                                                         - ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE + 1,
                                 .nx_packet_length = gs_mtu - ETHERNET_FRAME_SIZE};
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_RARP_SEND,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_packet = &outgoing_packet,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEthInit, outgoing_ipv6_packet_up_to_mtu_succeeds)
{
    tx_thread_preemption_change_fake.return_val = TX_SUCCESS;
    tx_thread_identify_fake.return_val = nullptr;

    UCHAR *payload_location = gs_packet_pool_buffer + ETHERNET_FRAME_SIZE + ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE;
    NX_PACKET outgoing_packet = {.nx_packet_prepend_ptr = payload_location,
                                 .nx_packet_append_ptr = payload_location + gs_mtu - ETHERNET_FRAME_SIZE
                                                         - ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE + 1,
                                 .nx_packet_length = gs_mtu - ETHERNET_FRAME_SIZE,
                                 .nx_packet_ip_version = NX_IP_VERSION_V6};
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_PACKET_SEND,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_packet = &outgoing_packet,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEth, unhandled_command_reported_as_failure)
{
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = UINT32_MAX, .nx_ip_driver_status = NX_DRIVER_ERROR};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_UNHANDLED_COMMAND, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEthInit, hw_tx_error_fails_ip_driver_send)
{
    tx_thread_preemption_change_fake.return_val = TX_SUCCESS;
    tx_thread_identify_fake.return_val = nullptr;

    UCHAR *payload_location = gs_packet_pool_buffer + ETHERNET_FRAME_SIZE + ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE;
    NX_PACKET outgoing_packet = {.nx_packet_prepend_ptr = payload_location,
                                 .nx_packet_append_ptr = payload_location + gs_mtu - ETHERNET_FRAME_SIZE
                                                         - ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE + 1,
                                 .nx_packet_length = gs_mtu - ETHERNET_FRAME_SIZE};
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_PACKET_SEND,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_packet = &outgoing_packet,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    ARM_ETH_MAC_SendFrame_fake.return_val = ARM_DRIVER_ERROR;

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_DRIVER_ERROR, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEthInit, no_memory_for_outgoing_packet_fails_ip_driver_send)
{
    tx_thread_preemption_change_fake.return_val = TX_SUCCESS;
    tx_thread_identify_fake.return_val = nullptr;
    ARM_ETH_MAC_SendFrame_fake.return_val = ARM_DRIVER_ERROR;

    UCHAR *payload_location = gs_packet_pool_buffer + ETHERNET_FRAME_SIZE + ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE;
    NX_PACKET outgoing_packet = {.nx_packet_prepend_ptr = payload_location,
                                 .nx_packet_append_ptr = payload_location + gs_mtu - ETHERNET_FRAME_SIZE
                                                         - ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE + 1,
                                 .nx_packet_length = gs_mtu - ETHERNET_FRAME_SIZE};
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_PACKET_SEND,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_packet = &outgoing_packet,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(nx_ip_driver.nx_ip_driver_status, NX_DRIVER_ERROR);
}

TEST_F(TestNxDriverCmsisEthInit, send_two_packets_subsequently_succeeds)
{
    tx_thread_preemption_change_fake.return_val = TX_SUCCESS;
    tx_thread_identify_fake.return_val = nullptr;

    UCHAR *payload_location = gs_packet_pool_buffer + ETHERNET_FRAME_SIZE + ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE;
    NX_PACKET outgoing_packet = {.nx_packet_prepend_ptr = payload_location,
                                 .nx_packet_append_ptr = payload_location + gs_mtu - ETHERNET_FRAME_SIZE
                                                         - ETHERNET_FRAME_ALIGNMENT_BYTES_SIZE + 1,
                                 .nx_packet_length = gs_mtu - ETHERNET_FRAME_SIZE};
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_PACKET_SEND,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_packet = &outgoing_packet,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
}

static UINT
nx_packet_allocate_custom_fake(NX_PACKET_POOL *pool_ptr, NX_PACKET **packet_ptr, ULONG packet_type, ULONG wait_option)
{
    static NX_PACKET incoming_packet = {0};
    // There is an adjustment made to the address of the prepend_ptr for the alignment of the Ethernet packet. The
    // packet type needs to be passed in a location 2 bytes further than where it will be placed in the header to
    // account for this.
    gs_packet_pool_buffer[14] = gs_incomming_packet_eth_type >> 8;
    gs_packet_pool_buffer[15] = gs_incomming_packet_eth_type & 0xFF;

    incoming_packet.nx_packet_prepend_ptr = gs_packet_pool_buffer;
    incoming_packet.nx_packet_append_ptr = gs_packet_pool_buffer + 1;
    incoming_packet.nx_packet_length = 0;

    *packet_ptr = &incoming_packet;

    return NX_SUCCESS;
}

TEST_F(TestNxDriverCmsisEthInit, incoming_packet_processing_is_successfully_deferred)
{
    ARM_ETH_MAC_GetRxFrameSize_fake.return_val = 128;
    nx_packet_allocate_fake.custom_fake = nx_packet_allocate_custom_fake;

    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_DEFERRED_PROCESSING,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    // Reception will only occur if a packet is pending
    EXPECT_EQ(1, _nx_ip_driver_deferred_processing_fake.call_count);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEth, no_deferred_processing_if_no_packet_pending)
{
    /* This test needs to do the Initialization within the test case as otherwise there will be
    a packet pending*/
    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_FALSE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_INITIALIZE,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    ARM_ETH_MAC_Initialize_fake.custom_fake = ARM_DRIVER_OK;
    ARM_ETH_PHY_Initialize_fake.return_val = ARM_DRIVER_OK;
    ARM_ETH_MAC_GetMacAddress_fake.custom_fake = get_ethernet_controller_mac_addr_custom_fake;

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);

    nx_ip_driver.nx_ip_driver_command = NX_LINK_INTERFACE_ATTACH;
    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);

    nx_ip_driver.nx_ip_driver_command = NX_LINK_ENABLE;
    ARM_ETH_PHY_GetLinkState_fake.return_val = ARM_ETH_LINK_UP;
    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
    EXPECT_EQ(NX_TRUE, nx_ip_driver_interface.nx_interface_link_up);

    nx_ip_driver.nx_ip_driver_command = NX_LINK_DEFERRED_PROCESSING;
    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    // Reception of packet only if notified
    EXPECT_EQ(0, _nx_ip_driver_deferred_processing_fake.call_count);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEthInit, processing_of_ip_packet_succeeds)
{
    ARM_ETH_MAC_GetRxFrameSize_fake.return_val = 128;
    nx_packet_allocate_fake.custom_fake = nx_packet_allocate_custom_fake;
    gs_incomming_packet_eth_type = ETHER_PACKET_TYPE_IP;

    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_DEFERRED_PROCESSING,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(1, _nx_ip_packet_deferred_receive_fake.call_count);
    EXPECT_EQ(0, nx_packet_release_fake.call_count);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEthInit, receive_ip_type_no_pool_space_for_packet)
{
    gs_incomming_packet_eth_type = ETHER_PACKET_TYPE_IP;
    nx_packet_allocate_fake.return_val = NX_NO_PACKET;

    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_DEFERRED_PROCESSING,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_packet = nullptr,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(0, _nx_ip_packet_deferred_receive_fake.call_count);
    EXPECT_EQ(0, nx_packet_release_fake.call_count);
}

TEST_F(TestNxDriverCmsisEthInit, processing_of_deferred_ipv6_packet_type_succeeds)
{
    ARM_ETH_MAC_GetRxFrameSize_fake.return_val = 128;
    nx_packet_allocate_fake.custom_fake = nx_packet_allocate_custom_fake;
    gs_incomming_packet_eth_type = ETHER_PACKET_TYPE_IPV6;

    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_DEFERRED_PROCESSING,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(1, _nx_ip_packet_deferred_receive_fake.call_count);
    EXPECT_EQ(0, nx_packet_release_fake.call_count);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEthInit, processing_of_deferred_arp_packet_type_succeeds)
{
    ARM_ETH_MAC_GetRxFrameSize_fake.return_val = 128;
    nx_packet_allocate_fake.custom_fake = nx_packet_allocate_custom_fake;
    gs_incomming_packet_eth_type = ETHER_PACKET_TYPE_ARP;

    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_DEFERRED_PROCESSING,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(1, _nx_arp_packet_deferred_receive_fake.call_count);
    EXPECT_EQ(0, nx_packet_release_fake.call_count);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEthInit, processing_of_deferred_rarp_packet_type_succeeds)
{
    ARM_ETH_MAC_GetRxFrameSize_fake.return_val = 128;
    nx_packet_allocate_fake.custom_fake = nx_packet_allocate_custom_fake;
    gs_incomming_packet_eth_type = ETHER_PACKET_TYPE_RARP;

    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_DEFERRED_PROCESSING,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(_nx_rarp_packet_deferred_receive_fake.call_count, 1);
    EXPECT_EQ(nx_packet_release_fake.call_count, 0);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEthInit, processing_of_deferred_unknown_packet_type_succeeds)
{
    ARM_ETH_MAC_GetRxFrameSize_fake.return_val = 128;
    nx_packet_allocate_fake.custom_fake = nx_packet_allocate_custom_fake;
    gs_incomming_packet_eth_type = UINT16_MAX;

    struct NX_IP_STRUCT nx_ip_driver_ptr = {0};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_TRUE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_DEFERRED_PROCESSING,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};

    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(nx_packet_release_fake.call_count, 1);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
}

TEST_F(TestNxDriverCmsisEth, on_link_down_event_sets_link_status_down)
{
    struct NX_IP_STRUCT nx_ip_driver_ptr = {.nx_ip_interface = {{
                                                .nx_interface_link_up = NX_TRUE // State before the event.
                                            }}};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_FALSE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_INITIALIZE,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};
    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);

    nx_ip_driver.nx_ip_driver_command = NX_LINK_INTERFACE_ATTACH;
    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);

    nx_ip_driver.nx_ip_driver_command = NX_LINK_ENABLE;
    ARM_ETH_PHY_GetLinkState_fake.return_val = ARM_ETH_LINK_UP;
    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
    EXPECT_EQ(NX_TRUE, nx_ip_driver_interface.nx_interface_link_up);

    nx_ip_driver.nx_ip_driver_command = NX_LINK_DISABLE;
    ARM_ETH_PHY_GetLinkState_fake.return_val = ARM_ETH_LINK_DOWN;
    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
    EXPECT_EQ(NX_FALSE, nx_ip_driver_interface.nx_interface_link_up);
}

TEST_F(TestNxDriverCmsisEth, on_link_up_event_sets_link_status_up)
{
    struct NX_IP_STRUCT nx_ip_driver_ptr = {.nx_ip_interface = {{
                                                .nx_interface_link_up = NX_TRUE // State before the event.
                                            }}};
    NX_INTERFACE nx_ip_driver_interface = {.nx_interface_link_up = NX_FALSE};
    NX_IP_DRIVER nx_ip_driver = {.nx_ip_driver_command = NX_LINK_INITIALIZE,
                                 .nx_ip_driver_status = NX_DRIVER_ERROR,
                                 .nx_ip_driver_ptr = &nx_ip_driver_ptr,
                                 .nx_ip_driver_interface = &nx_ip_driver_interface};
    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);

    nx_ip_driver.nx_ip_driver_command = NX_LINK_INTERFACE_ATTACH;
    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);

    nx_ip_driver.nx_ip_driver_command = NX_LINK_ENABLE;
    ARM_ETH_PHY_GetLinkState_fake.return_val = ARM_ETH_LINK_UP;
    nx_driver_cmsis_ip_link_fsm(&nx_ip_driver);
    EXPECT_EQ(NX_SUCCESS, nx_ip_driver.nx_ip_driver_status);
    EXPECT_EQ(NX_TRUE, nx_ip_driver_interface.nx_interface_link_up);
}
